home *** CD-ROM | disk | FTP | other *** search
/ PCGUIA 127 / PC Guia 127.iso / Software / Utils / NEW - Stylish / Bin / stylish-0.2.1-fx+fl+tb.xpi / chrome / stylish.jar / content / stylish.js < prev    next >
Text File  |  2006-01-24  |  19KB  |  555 lines

  1. /*
  2.     This code is licensed under the GPL (http://www.gnu.org/copyleft/gpl.txt) 
  3.     Author: Jason Barnabe <jason_barnabe@fastmail.fm>
  4.     Release date: Oct 9, 2005
  5. */
  6.  
  7. function Stylish() {
  8.  
  9.     this.currentURI = null;
  10.     this.CSSXULNS = "@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);";
  11.     this.CSSHTMLNS = "@namespace url(http://www.w3.org/1999/xhtml);";
  12.     this.documentRulePrefix = "@-moz-document ";
  13.  
  14.     this.manageInit = function() {
  15.         var tree = document.getElementById("styles");
  16.         tree.datasources = stylishCommon.datasourceURI;
  17.     }
  18.  
  19.     //fill the values
  20.     this.editInit = function() {
  21.         var o = window.arguments[0];
  22.         if ("uri" in o) {
  23.             this.currentURI = o.uri;
  24.             var node = stylishCommon.ds.getNode(o.uri);
  25.             document.getElementById("description").value = node.getTarget(stylishCommon.descriptionURI).getValue();
  26.             document.getElementById("enabled").checked = (node.getTarget(stylishCommon.enabledURI).getValue() == "true");
  27.             document.getElementById("code").value = node.getTarget(stylishCommon.codeURI).getValue();
  28.         }
  29.         if ("code" in o) {
  30.             document.getElementById("code").value = o.code;
  31.         }
  32.     }
  33.  
  34.  
  35.     //returns an array of the uris of the selected styles
  36.     this.getSelectedStyles = function() {
  37.         var uris = [];
  38.         var tree = document.getElementById("styles");
  39.         var rangeCount = tree.view.selection.getRangeCount();
  40.         for (var i = 0; i < rangeCount; i++) {
  41.             var start = {};
  42.             var end = {};
  43.             tree.view.selection.getRangeAt(i,start,end);
  44.             for (var c = start.value; c <= end.value; c++) {
  45.                 uris.push(tree.view.getItemAtIndex(c).id);
  46.                 //dont-build-content version
  47.                 //uris.push(tree.view.getResourceAtIndex(c).Value);
  48.             }
  49.         }
  50.         return uris;
  51.     }
  52.  
  53.     //Returns an array of objects representing the selection ranges
  54.     this.getSelectionRanges = function() {
  55.         var ranges = [];
  56.         var tree = document.getElementById("styles");
  57.         var rangeCount = tree.view.selection.getRangeCount();
  58.         for (var i = 0; i < rangeCount; i++) {
  59.             var start = {};
  60.             var end = {};
  61.             tree.view.selection.getRangeAt(i,start,end);
  62.             ranges[ranges.length] = {start: start, end: end};
  63.         }
  64.         return ranges;
  65.     }
  66.  
  67.     this.setSelectionRanges = function(ranges) {
  68.         var tree = document.getElementById("styles");
  69.         for (var i = 0; i < ranges.length; i++) {
  70.             tree.view.selection.rangedSelect(ranges[i].start.value, ranges[i].end.value, true);
  71.         }
  72.     }
  73.  
  74.     this.getTopVisibleRow = function() {
  75.         return document.getElementById("styles").treeBoxObject.getFirstVisibleRow();
  76.     }
  77.  
  78.     this.setTopVisibleRow = function(topVisibleRow) {
  79.         return document.getElementById("styles").treeBoxObject.scrollToRow(topVisibleRow);
  80.     }
  81.  
  82.     this.handleEnableButtonClick = function() {
  83.         //we want to be in the same place after the refresh
  84.         var ranges = this.getSelectionRanges();
  85.         var topVisibleRow = this.getTopVisibleRow();
  86.  
  87.         this.toggleDisable();
  88.  
  89.         this.setSelectionRanges(ranges);
  90.         this.setTopVisibleRow(topVisibleRow);
  91.     }
  92.  
  93.     this.toggleDisable = function() {
  94.         var styles = this.getSelectedStyles();
  95.         for (var i = 0; i < styles.length; i++) {
  96.             var node = stylishCommon.ds.getNode(styles[i]);
  97.             stylishCommon.toggleNodeDisable(node, false);
  98.         }
  99.         stylishCommon.ds.save();
  100.         stylishCommon.ds.refresh(true);
  101.     }
  102.  
  103.     //open the add dialog
  104.     this.openAdd = function() {
  105.         window.openDialog("chrome://stylish/content/stylish-edit.xul", "stylishEdit", "chrome,resizable", {});
  106.     }
  107.  
  108.     this.handleEditButtonClick = function() {
  109.         this.openEdit(this.getSelectedStyles()[0]);
  110.     }
  111.  
  112.     //open the edit dialog
  113.     this.openEdit = function(uri) {
  114.         window.openDialog("chrome://stylish/content/stylish-edit.xul", "stylishEdit", "chrome,resizable", {uri: uri});
  115.     }
  116.  
  117.     //validate the user entries, throws an exception on invalid
  118.     this.update = function(uri, description, enabled, code, callBack) {
  119.         if (description == null || description == "") {
  120.             document.getElementById("description").focus();
  121.             throw document.getElementById("strings").getString("blankDescription");
  122.         }
  123.         if (code == null || code == "") {
  124.             document.getElementById("code").focus();
  125.             throw document.getElementById("strings").getString("blankCode");
  126.         }
  127.         //loadStylesheet will fire the save and close stuff if everything's ok
  128.         this.loadStylesheet(code);
  129.     }
  130.  
  131.     this.applyStyle = function(siteRules) {
  132.         var uri = this.currentURI;
  133.         var description = document.getElementById("description").value;
  134.         var enabled = document.getElementById("enabled").checked;
  135.         var code = document.getElementById("code").value;
  136.         if (uri) {
  137.             var node = stylishCommon.ds.getNode(uri);
  138.             //remove the old code from the list of registered sheets
  139.             if (node.getTarget(stylishCommon.enabledURI).getValue() == "true") {
  140.                 stylishCommon.unregisterNode(node);
  141.             }
  142.             node.modifyTarget(stylishCommon.descriptionURI, node.getTarget(stylishCommon.descriptionURI), description);
  143.             node.modifyTarget(stylishCommon.enabledURI, node.getTarget(stylishCommon.enabledURI), enabled ? "true" : "false");
  144.             node.modifyTarget(stylishCommon.codeURI, node.getTarget(stylishCommon.codeURI), code);
  145.  
  146.             //remove site rules
  147.  
  148.             var targets = node.getTargets(stylishCommon.siteURLURI);
  149.             while (targets.hasMoreElements()) {
  150.                 var target = targets.getNext();
  151.                 node.removeTarget(stylishCommon.siteURLURI, target);
  152.             }
  153.             targets = node.getTargets(stylishCommon.siteURLPrefixURI);
  154.             while (targets.hasMoreElements()) {
  155.                 var target = targets.getNext();
  156.                 node.removeTarget(stylishCommon.siteURLPrefixURI, target);
  157.             }
  158.             targets = node.getTargets(stylishCommon.siteDomainURI);
  159.             while (targets.hasMoreElements()) {
  160.                 var target = targets.getNext();
  161.                 node.removeTarget(stylishCommon.siteDomainURI, target);
  162.             }
  163.         } else {
  164.             //add the node
  165.             var container = stylishCommon.ds.getNode(stylishCommon.containerURI);
  166.             var node = stylishCommon.ds.getAnonymousNode();
  167.             node.addTarget(stylishCommon.descriptionURI, description);
  168.             node.addTarget(stylishCommon.enabledURI, enabled ? "true" : "false");
  169.             node.addTarget(stylishCommon.codeURI, code);
  170.             //for some reason, this will throw if you delete everything then add in the same session.
  171.             container.addChild(node);
  172.             uri = stylishCommon.getNodeURI(node);
  173.         }
  174.  
  175.         //add the site rules
  176.         for (var i = 0; i < siteRules.length; i++) {
  177.             switch (siteRules[i].type) {
  178.                 case "url":
  179.                     node.addTarget(stylishCommon.siteURLURI, siteRules[i].site);
  180.                     break;
  181.                 case "url-prefix":
  182.                     node.addTarget(stylishCommon.siteURLPrefixURI, siteRules[i].site);
  183.                     break;
  184.                 case "domain":
  185.                     node.addTarget(stylishCommon.siteDomainURI, siteRules[i].site);
  186.                     break;
  187.                 default:
  188.                     alert("Unrecognized site rule type '" + siteRules[i].type + "'.");
  189.             }
  190.         }
  191.  
  192.         if (enabled) {
  193.             stylishCommon.registerNode(node);
  194.         }
  195.  
  196.         //save rdf file
  197.         stylishCommon.ds.save();
  198.         stylishCommon.ds.refresh(true);
  199.         window.close();
  200.     }
  201.  
  202.     this.editOK = function() {
  203.         try {
  204.             this.update(this.currentURI, document.getElementById("description").value, document.getElementById("enabled").checked, document.getElementById("code").value);
  205.         } catch (ex) {
  206.             alert(ex);
  207.         }
  208.         //cancel close will be handled further down the chain
  209.         return false;
  210.     }
  211.  
  212.     //delete all selected styles
  213.     this.deleteStyle = function() {
  214.         var styles = this.getSelectedStyles();
  215.         for (var i = 0; i < styles.length; i++) {
  216.             var node = stylishCommon.ds.getNode(styles[i]);
  217.             //unapply the style if it's applied
  218.             if (node.getTarget(stylishCommon.enabledURI).getValue() == "true") {
  219.                 stylishCommon.unregisterNode(node);
  220.             }
  221.             //delete it from the file
  222.             stylishCommon.ds.deleteRecursive(node);
  223.         }
  224.         stylishCommon.ds.save();
  225.         stylishCommon.ds.refresh(true);
  226.  
  227.         //XXX there's a bug somewhere that deleting a style will make all the styles below it not display. to work around this, refresh the tree
  228.         //remember scroll position. don't remember selection, because everything selected will have been deleted!
  229.         var topVisibleRow = this.getTopVisibleRow();
  230.  
  231.         //refresh
  232.         document.getElementById("styles").datasources = "rdf:null";
  233.         stylish.manageInit();
  234.  
  235.         setTimeout("stylish.setTopVisibleRow(" + topVisibleRow + ")", 100);
  236.     }
  237.  
  238.     this.styleListKeyPress = function(aEvent) {
  239.         //delete
  240.         if (aEvent.keyCode == 46) {
  241.             stylish.deleteStyle();
  242.         }
  243.     }
  244.  
  245.     //Handles changes in selection for the manage dialog tree.
  246.     this.changeSelection = function() {
  247.         var edit = document.getElementById("edit");
  248.         var deleteB = document.getElementById("delete");
  249.         var disable = document.getElementById("disable");
  250.         switch (this.getSelectedStyles().length) {
  251.             case 0:
  252.                 edit.disabled = true;
  253.                 deleteB.disabled = true;
  254.                 disable.disabled = true;
  255.                 break;
  256.             case 1:
  257.                 edit.disabled = false;
  258.                 deleteB.disabled = false;
  259.                 disable.disabled = false;
  260.                 break;
  261.             default:
  262.                 edit.disabled = true;
  263.                 deleteB.disabled = false;
  264.                 disable.disabled = false;
  265.                 break;
  266.         }
  267.     }
  268.  
  269.   //Insert the snippet at the start of the code textbox or highlight it if it's already in there
  270.     this.insertCodeAtStart = function(snippet) {
  271.         var codeElement = document.getElementById("code")
  272.         var position = codeElement.value.indexOf(snippet);
  273.         if (position == -1) {
  274.             //insert the code
  275.             //put some line breaks in if there's already code there
  276.             if (codeElement.value.length > 0) {
  277.                 codeElement.value = snippet + "\n" + codeElement.value;
  278.             } else {
  279.                 codeElement.value = snippet + "\n";
  280.             }
  281.         }
  282.         //highlight it
  283.         codeElement.setSelectionRange(snippet.length + 1, snippet.length + 1);
  284.         codeElement.focus();
  285.     }
  286.  
  287.     this.openSitesDialog = function() {
  288.         window.openDialog("chrome://stylish/content/stylish-specify-sites.xul", "stylishSpecifySites", "chrome,modal,resizable", this.applySpecifySite);
  289.     }
  290.  
  291.     //Process the return from the specify site dialog
  292.     this.applySpecifySite = function(data) {
  293.         if (data.length == 0) {
  294.             return;
  295.         }
  296.         var selector = "";
  297.         for (var i = 0; i < data.length; i++) {
  298.             if (selector != "") {
  299.                 selector += ", ";
  300.             }
  301.             selector += data[i].type + "(" + data[i].site + ")";
  302.         }
  303.         selector = "@-moz-document " + selector + " {\n";
  304.  
  305.         var codeElement = document.getElementById("code");
  306.         if (codeElement.selectionStart != codeElement.selectionEnd) {
  307.             //there's a selection, so let's cram the selection inside
  308.             var selection = codeElement.value.substring(codeElement.selectionStart, codeElement.selectionEnd);
  309.             var newValue = "";
  310.             //if there's stuff before the selection, include whitespace
  311.             if (codeElement.selectionStart > 0) {
  312.                 newValue = codeElement.value.substring(0, codeElement.selectionStart) + "\n";
  313.             }
  314.             newValue += selector;
  315.             var newCaretPosition = newValue.length;
  316.             newValue += selection + "\n}";
  317.             //if there's stuff after the selection, include whitespace
  318.             if (codeElement.selectionEnd < codeElement.value.length) {
  319.                 newValue += "\n" + codeElement.value.substring(codeElement.selectionEnd, codeElement.value.length);
  320.             }
  321.         } else {
  322.             //there's no selection, just put it at the end
  323.             //if there's stuff in the textbox, add some whitespace
  324.             if (codeElement.value.length > 0) {
  325.                 var newValue = codeElement.value + "\n" + selector;
  326.                 var newCaretPosition = newValue.length;
  327.                 newValue += "\n}";
  328.             } else {
  329.                 var newValue = selector;
  330.                 var newCaretPosition = newValue.length;
  331.                 newValue += "\n}";
  332.             }
  333.         }
  334.  
  335.         codeElement.value = newValue;
  336.         codeElement.setSelectionRange(newCaretPosition, newCaretPosition);
  337.         codeElement.focus();
  338.     }
  339.  
  340.     this.handleStyleListClick = function(event) { 
  341.         var tree = document.getElementById("styles");
  342.     var row = {}, col = {}, obj = {};
  343.     tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
  344.         //if the user clicked on the enabled column, toggle enabled
  345.         if (col.value && col.value.id == "enabled-label") {
  346.             //remember our position
  347.             var ranges = this.getSelectionRanges();
  348.             var topVisibleRow = this.getTopVisibleRow();
  349.  
  350.             var uri = tree.view.getItemAtIndex(row.value).id;
  351.             var node = stylishCommon.ds.getNode(uri);
  352.             stylishCommon.toggleNodeDisable(node, true);
  353.  
  354.             this.setSelectionRanges(ranges);
  355.             this.setTopVisibleRow(topVisibleRow);
  356.  
  357.             return;
  358.         }
  359.     }
  360.  
  361. /*    this.handleStyleListMouseDown = function(event) {
  362.         var tree = document.getElementById("styles");
  363.     var row = {}, col = {}, obj = {};
  364.     tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
  365.         //if the user moused down on the enabled column, don't select the row
  366.         if (col.value && col.value.id == "enabled-label") {
  367.             event.preventDefault();
  368.             event.stopPropagation();
  369.             return;
  370.         }
  371.     }*/
  372.  
  373.     this.handleStyleListDoubleClick = function(event) {
  374.         /*
  375.         //dont-build-content version
  376.         //only do something in the main area
  377.         if (event.target.nodeName != "treechildren") {
  378.             return;
  379.         }
  380.         //XXX this isn't perfect. for example, selecting an item then double-clicking a blank spot should
  381.         //bring up an add dialog, not an edit for the selected item
  382.         var uris = this.getSelectedStyles();
  383.         switch (uris.length) {
  384.             case 0:
  385.                 stylish.openAdd();
  386.                 break;
  387.             case 1:
  388.                 stylish.openEdit(uris[0]);
  389.                 break;
  390.             default:
  391.                 //ambiguous, don't do anything
  392.         }
  393.         */
  394.         var tree = document.getElementById("styles");
  395.     var row = {}, col = {}, obj = {};
  396.     tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
  397.         if (row.value == -1) {
  398.             //if the user double-clicked a blank spot, they want to add
  399.             stylish.openAdd()
  400.         } else {
  401.             //if they double-clicked a row in a place other than the enabled column, they want to edit
  402.             if (!col.value || col.value.id != "enabled-label") {
  403.                 stylish.openEdit(tree.view.getItemAtIndex(row.value).id);
  404.             }
  405.         }
  406.     }
  407.  
  408.     this.loadStylesheet = function(css) {
  409.         //we want to check for errors
  410.         var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
  411.         var errorListener = new StylishCSSErrorListener()
  412.  
  413.         //make a fake document and apply the style to it. this will throw errors and give us a stylesheet document
  414.         var doc = document.implementation.createDocument(document.xmlns, "stylish-parse", document.documentType);
  415.         var link = doc.createElementNS(stylishCommon.HTMLNS, "link");
  416.         link.rel = "stylesheet";
  417.         link.type = "text/css";
  418.         link.href = stylishCommon.codePrefix + css;
  419.  
  420.         //stylesheet loading is asynchronous
  421.         consoleService.registerListener(errorListener);
  422.         var loadedListener = new StylishStylesheetLoadedListener(doc, this.stylesheetLoaded, errorListener);
  423.         //this actually loads the sheet
  424.         doc.documentElement.appendChild(link);
  425.         //now start checking for completion
  426.         loadedListener.checkStyleLoaded();
  427.     }
  428.  
  429.     this.getDocRules = function(stylesheet) {
  430.         //get an array of document rules
  431.         var docRules = [];
  432.         for (var i = 0; i < stylesheet.cssRules.length; i++) {
  433.             var rule = stylesheet.cssRules[i];
  434.             var isDocRule;
  435.             try {
  436.                 rule.QueryInterface(Components.interfaces.nsIDOMCSSMozDocumentRule);
  437.                 isDocRule = true;
  438.             } catch (ex) {
  439.                 if (ex.name == "NS_NOINTERFACE") {
  440.                     isDocRule = false;
  441.                 } else {
  442.                     throw ex;
  443.                 }
  444.             }
  445.  
  446. /*            if (rule.type == Components.interfaces.nsIDOMCSSRule.UNKNOWN_RULE) {
  447.                 var mozDocPosition = rule.cssText.indexOf(this.documentRulePrefix);
  448.                 //if this actually contains a document rule
  449.                 if (mozDocPosition > -1) {*/
  450.             if (isDocRule) {
  451.                 //get an array of the sites it applies to
  452.                 var mozDocPosition = rule.cssText.indexOf(this.documentRulePrefix);
  453.                 if (mozDocPosition == -1) {
  454.                     alert("Rule QIs to moz-document but moz-document string not found.");
  455.                     return docRules;
  456.                 }
  457.                 var mozDocEnd = rule.cssText.indexOf(" {");
  458.                 var sitesString = rule.cssText.substring(mozDocPosition + this.documentRulePrefix.length, mozDocEnd - 1);
  459.                 //this could fail if a url contains ", " in it. probably won't happen
  460.                 var sites = sitesString.split(", ");
  461.                 for (var j = 0; j < sites.length; j++) {
  462.                     var openParenthesis = sites[j].indexOf("(\"");
  463.                     var type = sites[j].substring(0, openParenthesis);
  464.                     //open parenthesis + the size of (". length - 1 for zero based - 1 for the closing parenthesis
  465.                     var site = sites[j].substring(openParenthesis + 2, sites[j].length - 2);
  466.                     docRules[docRules.length] = {type: type, site: site};
  467.                 }
  468.             }
  469.         }
  470.         return docRules;
  471.     }
  472.  
  473.     this.stylesheetLoaded = function(success, data) {
  474.         //did the stylesheet load fail?
  475.         if (!success) {
  476.             throw data.exception;
  477.         }
  478.         //did the stylesheet load succeed, but the stylesheet contains errors?
  479.         if (data.errors.length > 0) {
  480.             //make a string containing a maximum number of errors
  481.             var errorString = null;
  482.             for (var i = 0; i < Math.min(data.errors.length, 3); i++) {
  483.                 if (errorString == null) {
  484.                     errorString = data.errors[i].message;
  485.                 } else {
  486.                     errorString += "\n\n" + data.errors[i].message;
  487.                 }
  488.             }
  489.             //does the user want to keep going?
  490.             if (!confirm(document.getElementById("strings").getFormattedString("invalidCode", [errorString]))) {
  491.                 return;
  492.             }
  493.         }
  494.         var docRules = stylish.getDocRules(data.stylesheet);
  495.         stylish.applyStyle(docRules);
  496.     }
  497.  
  498. }
  499.  
  500. function StylishCSSErrorListener() {
  501.     this.errors = [];
  502.     this.QueryInterface = function(aIID) {
  503.         if (aIID.equals(Components.interfaces.nsIConsoleListener) ||
  504.             aIID.equals(Components.interfaces.nsISupports))
  505.             return this;
  506.         throw Components.results.NS_NOINTERFACE;
  507.     }
  508.     this.observe = function(message) {
  509.         this.errors[this.errors.length] = message;
  510.     }
  511. }
  512.  
  513. function StylishStylesheetLoadedListener(doc, callBack, errorListener) {
  514.     stylish.loadedListener = this;
  515.     this.doc = doc; 
  516.     this.callBack = callBack;
  517.     this.errorListener = errorListener;
  518.     this.checkStyleLoaded = function() {
  519.         //any errors in here will seriously bork us. make sure to at least tell the user
  520.         try {
  521.             try {
  522.                 var stylesheet = stylish.loadedListener.doc.QueryInterface(Components.interfaces.nsIDOMDocumentStyle).styleSheets[0];
  523.                 //this'll throw if it's not done loading
  524.                 stylesheet.cssRules.length;
  525.             } catch (ex) {
  526.                 if (ex.name == "NS_ERROR_DOM_INVALID_ACCESS_ERR") {
  527.                     //try again
  528.                     setTimeout(stylish.loadedListener.checkStyleLoaded, 100);
  529.                 } else {
  530.                     //some other error happened
  531.                     stylish.loadedListener.unregisterErrorListener();
  532.                     var data = {exception: ex, stylesheet: stylesheet, errors: stylish.loadedListener.errorListener.errors};
  533.                     stylish.loadedListener.callBack(false, data);
  534.                     stylish.loadedListener.destroy();
  535.                 }
  536.                 return;
  537.             }
  538.             stylish.loadedListener.unregisterErrorListener();
  539.             stylish.loadedListener.callBack(true, {stylesheet: stylesheet, errors: stylish.loadedListener.errorListener.errors});
  540.             stylish.loadedListener.destroy();
  541.         } catch (ex) {
  542.             alert(ex);
  543.         }
  544.     }
  545.     this.unregisterErrorListener = function() {
  546.         var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
  547.         consoleService.unregisterListener(stylish.loadedListener.errorListener);
  548.     }
  549.     this.destroy = function() {
  550.         stylish.loadedListener = null;
  551.     }
  552. }
  553.  
  554. var stylish = new Stylish();
  555.